/*
	Copyright (C) 2010 2011 Rémi Thebault

	This program is free software; you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation; either version 3 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

#include <QDebug>
#include <QToolBar>
#include <QFileDialog>
#include <QMessageBox>
#include <QVariant>

#include <limits>
#include <iostream>
#include <stdexcept>

#include <procycle/task.h>
#include <procycle/atomtask.h>
#include <procycle/cycle.h>

#include "procycle.h"
#include "tasktreeview.h"
#include "tasktreemodel.h"
#include "ganttview.h"
#include "ganttscene.h"
#include "taskselectionmodel.h"
#include "aboutdialog.h"
#include "taskpropertiesdialog.h"


ProCycle::ProCycle(int argc, char** argv) :
	QMainWindow(0),
	m_cycle(0)
{
	m_cycle = new PC::Cycle();
	m_cycle->setName("Cycle");
	m_cycle->setNum(0);
	m_cycle->schedule();

	createUI();

	showMaximized();

	m_ganttScene->initSizePolicy(m_ganttView->size());
	QObject::connect(m_ganttView, SIGNAL(resized(QSize)), m_ganttScene, SLOT(processViewResize(QSize)));

	if (argc > 1) {
		if (QFile::exists(argv[1])) {
			try {
				{
					PC::SignalDisabler sd (m_cycle);
					m_cycle->loadFromFile(argv[1]);
					m_cycle->schedule();
					m_cycle->setNum(0);
				}
				m_ganttScene->resetCycle(m_cycle);
				m_taskTreeModel->resetCycle(m_cycle);
				m_taskTreeView->syncExpansion();
				std::cout << "loaded " << argv[1] << std::endl;
			}
			catch (...) {
				QString message = QString (
							tr("An error occurred while loading %1\nAborting.")
				).arg(argv[1]);
				QMessageBox::critical(this,
						tr("Error occurred..."),
						message);
			}
		}
		else {
			std::cerr << argv[1] << " doesn't exist" << std::endl;
		}
	}
}

ProCycle::~ProCycle()
{
	delete m_cycle;
}


void ProCycle::createUI() {

	ui.setupUi(this);

	QComboBox* cb = new QComboBox(ui.toolBar);
	ui.toolBar->insertWidget(ui.actionNewCycle, cb);
	cb->addItem(tr("Untitled"));

	m_actionDeleteLink = new QAction(this);
	m_actionDeleteLink->setObjectName("actionDeleteLink");
	QIcon icon;
	icon.addFile(":/images/link_del.png", QSize(), QIcon::Normal, QIcon::Off);
	m_actionDeleteLink->setIcon(icon);
	m_actionDeleteLink->setText(tr("Delete Link"));
	m_actionDeleteLink->setToolTip(tr("Delete Link"));
	m_actionDeleteLink->setEnabled(false);
	m_spinLinkLag = new QSpinBox(this);
	m_spinLinkLag->setRange(std::numeric_limits<int>::min(),
							std::numeric_limits<int>::max());
	m_spinLinkLag->setEnabled(false);
	m_comboLinkType = new QComboBox(this);
	QIcon iconFS;
	iconFS.addFile(":/images/linktype_FS.png", QSize(), QIcon::Normal, QIcon::Off);
	QIcon iconSS;
	iconSS.addFile(":/images/linktype_SS.png", QSize(), QIcon::Normal, QIcon::Off);
	QIcon iconSF;
	iconSF.addFile(":/images/linktype_SF.png", QSize(), QIcon::Normal, QIcon::Off);
	QIcon iconFF;
	iconFF.addFile(":/images/linktype_FF.png", QSize(), QIcon::Normal, QIcon::Off);
	m_comboLinkType->addItem(iconFS, tr("Fin. -> St."), QVariant(int(PC::Link::TYPE_FS)));
	m_comboLinkType->addItem(iconSS, tr("St. -> St."), QVariant(int(PC::Link::TYPE_SS)));
	m_comboLinkType->addItem(iconSF, tr("St. -> Fin."), QVariant(int(PC::Link::TYPE_SF)));
	m_comboLinkType->addItem(iconFF, tr("Fin. -> Fin."), QVariant(int(PC::Link::TYPE_FF)));
	m_comboLinkType->setEnabled(false);
	m_linkToolBar = new QToolBar(this);
	m_linkToolBar->setObjectName("linkEditionToolBar");
	m_linkToolBar->addAction(m_actionDeleteLink);
	m_linkToolBar->addWidget(m_spinLinkLag);
	m_linkToolBar->addWidget(m_comboLinkType);
	m_linkToolBar->setMovable(true);
	m_linkToolBar->hide();
	addToolBar(m_linkToolBar);
	QObject::connect(m_actionDeleteLink, SIGNAL(triggered()), this, SLOT(deleteLink()));
	QObject::connect(m_spinLinkLag, SIGNAL(valueChanged(int)), this, SLOT(changeLinkLag(int)));
	QObject::connect(m_comboLinkType, SIGNAL(currentIndexChanged(int)), this, SLOT(changeLinkType(int)));


	m_selectionModel = new TaskSelectionModel(m_cycle, this);

	m_taskTreeView = new TaskTreeView(this, ui.splitter);
	ui.splitter->addWidget(m_taskTreeView);
	m_ganttView = new GanttView(this, ui.splitter);
	ui.splitter->addWidget(m_ganttView);


	m_taskTreeModel = new TaskTreeModel(m_cycle, this);
	QAbstractItemModel* old = m_taskTreeView->model();
	m_taskTreeView->setModel(m_taskTreeModel);
	delete old;
	QObject::connect(m_taskTreeModel, SIGNAL(columnsChanged()),
					 static_cast<TaskTreeHeader*>(m_taskTreeView->header()), SLOT(resizeColumns()));
	QList<PC::Task::Data> cols;
	cols << PC::Task::DATA_NAME << PC::Task::DATA_LENGTH << PC::Task::DATA_START << PC::Task::DATA_FINISH;
	m_taskTreeModel->setColumns(cols);
	m_taskTreeView->header()->moveSection(1, 0);


	m_taskTreeView->expandAllTasks();

	m_ganttScene = new GanttScene(this, QRectF(0, 0, m_ganttView->width(), m_ganttView->height()), this);
	m_ganttView->setScene(m_ganttScene);


	QObject::connect(m_cycle, SIGNAL(taskValueChanged(PC::Task*,PC::Task::Data)),
					 m_taskTreeModel, SLOT(processTaskValueChanged(PC::Task*,PC::Task::Data)));
	QObject::connect(m_cycle, SIGNAL(taskTimeChanged(PC::Task*)),
					 m_taskTreeModel, SLOT(processTaskTimeChanged(PC::Task*)));
	QObject::connect(m_cycle, SIGNAL(taskCreated(PC::MacroTask*,PC::Task*)),
					 m_taskTreeModel, SLOT(processTaskCreated(PC::MacroTask*,PC::Task*)));
	QObject::connect(m_cycle, SIGNAL(taskWillDelete(PC::Task*)),
					 m_taskTreeModel, SLOT(processTaskWillDelete(PC::Task*)));
	QObject::connect(m_cycle, SIGNAL(taskTransformed(PC::MacroTask*,int)),
					 m_taskTreeModel, SLOT(processTaskTransformed(PC::MacroTask*,int)));
	QObject::connect(m_cycle, SIGNAL(taskMoved(PC::MacroTask*,PC::Task*,int,int)),
					 m_taskTreeModel, SLOT(processTaskMoved(PC::MacroTask*,PC::Task*,int,int)));

	QObject::connect(m_cycle, SIGNAL(scheduled()), m_ganttScene, SLOT(processScheduled()));
	QObject::connect(m_cycle, SIGNAL(taskValueChanged(PC::Task*,PC::Task::Data)),
					 m_ganttScene, SLOT(processTaskValueChanged(PC::Task*,PC::Task::Data)));
	QObject::connect(m_cycle, SIGNAL(taskTimeChanged(PC::Task*)),
					 m_ganttScene, SLOT(processTaskTimeChanged(PC::Task*)));
	QObject::connect(m_cycle, SIGNAL(taskCreated(PC::MacroTask*,PC::Task*)),
					 m_ganttScene, SLOT(processTaskCreated(PC::MacroTask*,PC::Task*)));
	QObject::connect(m_cycle, SIGNAL(taskWillDelete(PC::Task*)),
					 m_ganttScene, SLOT(processTaskWillDelete(PC::Task*)));
	QObject::connect(m_cycle, SIGNAL(taskTransformed(PC::MacroTask*,int)),
					 m_ganttScene, SLOT(processTaskTransformed(PC::MacroTask*,int)));
	QObject::connect(m_cycle, SIGNAL(taskMoved(PC::MacroTask*,PC::Task*,int,int)),
					 m_ganttScene, SLOT(processTaskMoved(PC::MacroTask*,PC::Task*,int,int)));
	QObject::connect(m_cycle, SIGNAL(linkCreated(PC::Link*)),
					 m_ganttScene, SLOT(processLinkCreated(PC::Link*)));
	QObject::connect(m_cycle, SIGNAL(linkWillDelete(PC::Link*)),
					 m_ganttScene, SLOT(processLinkWillDelete(PC::Link*)));

	QObject::connect(m_cycle, SIGNAL(taskWillDelete(PC::Task*)),
					 m_selectionModel, SLOT(taskWillDelete(PC::Task*)));
	QObject::connect(m_cycle, SIGNAL(linkWillDelete(PC::Link*)),
					 m_selectionModel, SLOT(linkWillDelete(PC::Link*)));




	QObject::connect(m_selectionModel, SIGNAL(taskSelectionChanged(PC::Task*,PC::Task*)),
					 m_taskTreeView, SLOT(selectTask(PC::Task*,PC::Task*)));
	QObject::connect(m_selectionModel, SIGNAL(taskSelectionChanged(PC::Task*,PC::Task*)),
					 m_ganttScene, SLOT(selectTask(PC::Task*,PC::Task*)));
	QObject::connect(m_selectionModel, SIGNAL(linkSelectionChanged(PC::Link*,PC::Link*)),
					 m_ganttScene, SLOT(selectLink(PC::Link*,PC::Link*)));
	QObject::connect(m_selectionModel, SIGNAL(taskSelectionChanged(PC::Task*,PC::Task*)),
					 this, SLOT(processSelectedTaskChanged(PC::Task*,PC::Task*)));
	QObject::connect(m_selectionModel, SIGNAL(linkSelectionChanged(PC::Link*,PC::Link*)),
					 this, SLOT(processSelectedLinkChanged(PC::Link*,PC::Link*)));

	QObject::connect(m_taskTreeView, SIGNAL(taskExpansionChanged(PC::Task*)),
					 m_ganttScene, SLOT(processExpansionChange(PC::Task*)));

	QObject::connect(ui.actionCriticalPath, SIGNAL(toggled(bool)), m_ganttScene, SLOT(toggleCriticalPath(bool)));
	QObject::connect(ui.actionTasksFloat, SIGNAL(toggled(bool)), m_ganttScene, SLOT(toggleTasksFloat(bool)));
	QObject::connect(ui.actionCycleOverlap, SIGNAL(toggled(bool)), m_ganttScene, SLOT(toggleCycleOverlap(bool)));
	QObject::connect(ui.actionZoomFit, SIGNAL(triggered()), m_ganttScene, SLOT(zoomFit()));
	QObject::connect(ui.actionZoomIn, SIGNAL(triggered()), m_ganttScene, SLOT(zoomIn()));
	QObject::connect(ui.actionZoomOut, SIGNAL(triggered()), m_ganttScene, SLOT(zoomOut()));
	QObject::connect(ui.actionZoomWindow, SIGNAL(toggled(bool)), m_ganttScene, SLOT(toggleZoomWindow(bool)));

	ui.splitter->setStretchFactor(0, 1);
	ui.splitter->setStretchFactor(1, 5);
}



void ProCycle::about() {
	AboutDialog dlg (this);
	dlg.setModal(true);
	dlg.show();
	dlg.exec();
}


void ProCycle::preferences() {

}


void ProCycle::quit() {
	close();
}


void ProCycle::importCycle() {
	QString filename = QFileDialog::getOpenFileName(this,
													QString(),
													m_cycle->preferredFile(),
													"ProCycle files (*.pc);;All files (*)");
	if (!filename.isNull()) {
		{
			PC::SignalDisabler sd (m_cycle);
			m_cycle->loadFromFile(filename);
			m_cycle->schedule();
			m_cycle->setNum(0);
		}
		m_ganttScene->resetCycle(m_cycle);
		m_taskTreeModel->resetCycle(m_cycle);
		m_taskTreeView->syncExpansion();
	}
}



void ProCycle::exportCycle() {
	QString filename = QFileDialog::getSaveFileName(
				this,
				QString(),
				m_cycle->preferredFile(),
				"ProCycle files (*.pc);;All files (*)");
	if (!filename.isNull()) {
		if(!filename.endsWith(".pc")) {
			filename.append(".pc");
		}
		try {
			m_cycle->saveToFile(filename);
		}
		catch (PC::FileError) {
			QMessageBox::critical(this,
						  tr("Error occurred..."),
						  tr("An error occurred while saving the file. Aborting."));
		}
	}
}



void
ProCycle::newTask() {
	QModelIndexList l = m_taskTreeView->selectionModel()->selectedIndexes();
	if (l.count() > 0) {
		QModelIndex i = l.at(0);
		TaskTreeItem* parentItem = static_cast<TaskTreeItem*>(i.internalPointer())->parent();
		PC::MacroTask* mt = dynamic_cast<PC::MacroTask*>(parentItem->task());
		if (mt) {
			mt->createAndInsertTask(i.row());
		}
	}
	else {
		m_cycle->createAndAppendTask();
	}
	m_cycle->setNum(0);
}

void
ProCycle::deleteTask() {
	QModelIndexList l = m_taskTreeView->selectionModel()->selectedIndexes();
	if (l.count() > 0) {
		QModelIndex i = l.at(0);
		TaskTreeItem* parentItem = static_cast<TaskTreeItem*>(i.internalPointer())->parent();
		PC::MacroTask* mt = dynamic_cast<PC::MacroTask*>(parentItem->task());
		if (mt) {
			mt->deleteTask(i.row());
		}
		m_cycle->setNum(0);
	}
}


void
ProCycle::indentTask() {
	QModelIndexList l = m_taskTreeView->selectionModel()->selectedIndexes();
	if (l.count() > 0) {
		QModelIndex i = l.at(0);
		PC::Task* task = m_taskTreeModel->taskForIndex(i);
		PC::MacroTask* mt = task->parent();

		if (mt) {
			mt->indentTask(task);
		}
		m_taskTreeView->expand(i.parent());

		updateActions();
		m_cycle->setNum(0);
	}
}


void
ProCycle::unindentTask() {
	QModelIndexList l = m_taskTreeView->selectionModel()->selectedIndexes();
	if (l.count() > 0) {
		QModelIndex i = l.at(0);
		QModelIndex pi = i.parent(); // parent
		PC::MacroTask* gpt = dynamic_cast<PC::MacroTask*>( // grand parent
					m_taskTreeModel->taskForIndex(pi.parent())
		);
		if (gpt) {
			gpt->unindentTask(pi.row(), i.row());
		}
		updateActions();
		m_cycle->setNum(0);
	}
}


void ProCycle::taskProperties() {
    if (m_selectionModel->hasSelection()) {
        if (PC::Task* t = m_selectionModel->selectedTask()) {
            TaskPropertiesDialog dlg (t, this);
            dlg.show();
            if (dlg.exec() == QDialog::Accepted) {
                t->setName(dlg.getName());
                if (!dlg.getComments().isEmpty()) {
                    t->setProperty("comments", dlg.getComments());
                }
                else {
                    t->setProperty("comments", QVariant());
                }
				QColor c = dlg.getColor();
				if (GanttScene::colorForTask(t) != c) {
					t->setProperty("color", c.name());
					m_ganttScene->update();
				}
            }
        }
    }
}


void ProCycle::deleteLink() {
	if (PC::Link* l = selectionModel()->selectedLink()) {
		m_cycle->deleteLink(l);
	}
}

void ProCycle::changeLinkLag(int newVal) {
    if (PC::Link* l = selectionModel()->selectedLink()) {
        l->setLag(newVal);
    }
}

void ProCycle::changeLinkType(int newIndex) {
    if (PC::Link* l = selectionModel()->selectedLink()) {
        l->setType(PC::Link::Type(m_comboLinkType->itemData(newIndex).toInt()));
    }
}

void ProCycle::toggleFullScreen(bool on) {
	if (on) {
		showFullScreen();
	}
	else {
		showNormal();
	}
}


void ProCycle::toggleTaskMode(bool on) {
	EditionMode oldMode = g_editMode;
	if (on) {
		ui.actionLinkMode->blockSignals(true);
		ui.actionLinkMode->setChecked(false);
		ui.actionLinkMode->blockSignals(false);
		g_editMode = EDIT_TASK;
	}
	else {
		g_editMode = EDIT_NONE;
	}
	editionModeChanged(oldMode);
}



void ProCycle::toggleLinkMode(bool on) {
	EditionMode oldMode = g_editMode;
	if (on) {
		ui.actionTaskMode->blockSignals(true);
		ui.actionTaskMode->setChecked(false);
		ui.actionTaskMode->blockSignals(false);
		g_editMode = EDIT_LINK;
	}
	else {
		g_editMode = EDIT_NONE;
	}
	editionModeChanged(oldMode);
}


void ProCycle::processSelectedTaskChanged(PC::Task* selected, PC::Task* unselected) {
	updateActions();
}


void ProCycle::processSelectedLinkChanged(PC::Link* selected, PC::Link* unselected) {
	if (selected) {
		m_actionDeleteLink->setEnabled(true);
		m_spinLinkLag->setEnabled(true);
		m_comboLinkType->setEnabled(true);
		m_spinLinkLag->setValue(selected->lag());
		for(int i=0; i<4; i++) {
			if (m_comboLinkType->itemData(i).toInt() == int(selected->type())) {
				m_comboLinkType->setCurrentIndex(i);
				break;
			}
		}
	}
	else {
		m_spinLinkLag->setValue(0);
		m_comboLinkType->setCurrentIndex(0);
		m_actionDeleteLink->setEnabled(false);
		m_spinLinkLag->setEnabled(false);
		m_comboLinkType->setEnabled(false);
	}
}



void ProCycle::updateActions() {
	if (selectionModel()->selectedTask()) {
		ui.actionDeleteTask->setEnabled(true);
		ui.actionIndentTask->setEnabled(selectionModel()->selectedTask()->canIndent());
		ui.actionUnindentTask->setEnabled(selectionModel()->selectedTask()->canUnindent());
        ui.actionTaskProperties->setEnabled(true);
	}
	else {
		ui.actionDeleteTask->setEnabled(false);
		ui.actionIndentTask->setEnabled(false);
		ui.actionUnindentTask->setEnabled(false);
        ui.actionTaskProperties->setEnabled(false);
	}
}



QString ProCycle::taskTreeHeaderColName(PC::Task::Data td) const {
	return m_taskTreeView->headerColName(td);
}


GanttAxis* ProCycle::ganttAxis() const {
	return m_ganttScene->axis();
}


void ProCycle::keyReleaseEvent(QKeyEvent *event) {
	QMainWindow::keyReleaseEvent(event);
	if (event->key() == Qt::Key_Escape) {
		if (selectionModel()->hasSelection()) {
			selectionModel()->clearSelection();
		}
	}
}


void ProCycle::editionModeChanged(EditionMode oldMode) {
	switch(oldMode) {
	case EDIT_LINK:
		m_linkToolBar->hide();
		break;
	}

	switch (g_editMode) {
	case EDIT_TASK:
		break;
	case EDIT_LINK:
		m_linkToolBar->show();
		break;
	}

	m_ganttScene->editModeChanged();
}
